#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#include "raylib.h"
#include "pub_lib.h"
#include "FreeImage.h"
#include "image.h"
#include "dpi_algo.h"

const int screenWidth = 1024;
const int screenHeight = 768;

cpu_image effect_data;
run_state gstate = STATE_IDLE;

char* hint_right = NULL;
char hint_left[128] = "image size";

char demo_image[] = "Fig0333(a)(test_pattern_blurring_orig).tif";
char switch_image[] = "Fig0427(a)(woman).tif";

static BYTE* gmorph_label_ary = NULL;
static morph_label* gmorph_label = NULL;
static BYTE* gresult = NULL;

#define MAX_CMD_NUM 9
const char command_str_en[MAX_CMD_NUM][MAX_HINT_STRING_LEN] =
{
	//9.2
	"1. Erosion", //example 9.1
	"2. Dilation", //example 9.2

	//9.3
	"3. Open and close", //example 9.4
	"4. Boundary Extraction", //example 9.5
	"5. Hole Filling", //example 9.6
	"6. detect foreign objects", //example 9.7

	//9.5.9, Morphological Reconstruction
	"7. Opening by reconstruction",
	"8. Filling Holes",
	"9. Border clearing"
};

const char command_str_cn[MAX_CMD_NUM][MAX_HINT_STRING_LEN] =
{
	"1. 腐蚀",
	"2. 膨胀",
	"3. 开操作和闭操作",
	"4. 边界提取",
	"5. 孔洞填充",
	"6. 检测包装食品",
	"7. 重建开操作",
	"8. 填充孔洞",
	"9. 边界清除"
};

void init_effect_data(void)
{
	memset(&effect_data, 0, sizeof(cpu_image));
	effect_data.win_width = screenWidth;
	effect_data.win_height = screenHeight;
	effect_data.fb_pixels = effect_data.win_width * effect_data.win_height;
	effect_data.startx = 10;
	effect_data.starty = 30;
	effect_data.left_max = get_max_pixel(screenWidth, 0.3f);
	effect_data.right_max = get_max_pixel(screenWidth, 0.7f);
	effect_data.right_image_max = effect_data.right_max;;

	effect_data.split_line = effect_data.startx + effect_data.left_max + 5;
	effect_data.lx = effect_data.startx;
	effect_data.rx = effect_data.split_line + 5;
}

void init_fb(void)
{
	effect_data.frame_buf = calloc(effect_data.fb_pixels, sizeof(Color));
}

static void extra_init(void)
{
	int w, h;
	w = effect_data.image_width;
	h = effect_data.image_height;

	if(!gmorph_label_ary)
		gmorph_label_ary = malloc(w * h);

	if(!gmorph_label)
		gmorph_label = malloc(MORPH_END_LABEL * sizeof(morph_label));

	if(!gresult)
		gresult = malloc(w * h);
}

static void extra_destroy(void)
{
	if(gmorph_label_ary)
		free(gmorph_label_ary);
	gmorph_label_ary = NULL;

	if(gmorph_label)
		free(gmorph_label);
	gmorph_label = NULL;

	if(!gresult)
		free(gresult);
	gresult = NULL;
}

/*
 * 打开图片，读入图片数据
 * 原始图片信息:  ori
 * 左边显示的小图片: left
 * 右边显示的大图片: right
 * 右边显示的大图片的更改后的数据: rchg
 * 右边显示的大图片，中间计算的结果：rchg_data
 * */
int init_effect(char* path, int right_window_num)
{
	effect_data.right_window_num = right_window_num;
	const char* ext = GetFileExtension(path);

	FIBITMAP* ori;
	ori = open_img(path);
	if (!ori)
		return -1;
	if (!strcmp(ext, ".tif")) {
		read_img(ori, &effect_data);
	}

	int size = effect_data.image_width * effect_data.image_height;
	effect_data.ori_data = malloc(size);
	effect_data.chg_data = malloc(size);
	init_image_datab(ori, effect_data.ori_data);

	effect_data.chg = copy_img(ori);
	effect_data.show_histo = 0;
	effect_data.order = 0;
	effect_data.ori = (void*)ori;
	effect_data.left = (void*)ori;
	effect_data.right = (void*)ori;
	//check if need scale of left image
	int max = effect_data.left_max; //最长或最宽不超过360
	int neww = max, newh = max;
		if ( effect_data.image_width > max || effect_data.image_height > max) {
		cal_scaled_val(max, effect_data.image_width, effect_data.image_height,\
				&neww, &newh);
		effect_data.left = FreeImage_Rescale(ori, neww, newh, FILTER_BILINEAR);
	}

	//check if need scale of right image
	if(right_window_num == 1)
		effect_data.right_image_max = get_max_pixel(screenWidth, 0.7f);
	else
		effect_data.right_image_max = split_right_window(effect_data.win_width, 0.7f);

	max = effect_data.right_image_max;
	neww = effect_data.image_width;
	newh = effect_data.image_height;
	if (effect_data.image_width > max || effect_data.image_height > max) {
		cal_scaled_val(max, effect_data.image_width, effect_data.image_height, \
			&neww, &newh);
		effect_data.right = FreeImage_Rescale(ori, neww, newh, FILTER_BILINEAR);
	}

	sprintf(hint_left, "ori:%dx%d  chg:%dx%d",
		effect_data.image_width, effect_data.image_height, neww, newh);

	effect_data.rchg = FreeImage_Copy(effect_data.right, 0, 0, neww, newh);
	effect_data.pri  = FreeImage_Copy(effect_data.right, 0, 0, neww, newh);

	//int data[rw][rh]
	effect_data.rw = neww;
	effect_data.rh = newh;
	effect_data.rchg_data = calloc(neww * newh, sizeof(float));
	effect_data.rchg_image = calloc(neww * newh, 1);
	extra_init();

	effect_data.image_size = GetPixelDataSize(effect_data.image_width,\
		effect_data.image_height, effect_data.format);
	effect_data.pix_len = get_pixel_bytes(effect_data.image_width,\
		effect_data.image_height, effect_data.image_size);

	return 0;
}

void unload_image(void)
{
	if(effect_data.left != effect_data.ori)
		close_img(effect_data.left);
	if(effect_data.right != effect_data.ori)
		close_img(effect_data.right);
	if(effect_data.ori)
		close_img(effect_data.ori);
	if(effect_data.chg)
		close_img(effect_data.chg);
	if(effect_data.rchg)
		close_img(effect_data.rchg);
	if(effect_data.pri)
		close_img(effect_data.pri);

	if(effect_data.ori_data)
		free(effect_data.ori_data);
	if(effect_data.chg_data)
		free(effect_data.chg_data);

	if(effect_data.rchg_data)
		free(effect_data.rchg_data);
	if(effect_data.rchg_image)
		free(effect_data.rchg_image);
	extra_destroy();
}

void destroy_effect(void)
{
	free(effect_data.frame_buf);
	unload_image();
}

void switch_next_image(char* filename, int right_window_num)
{
	int ret;
	char path[256];

	clear_fb(&effect_data, RAYWHITE);
	unload_image();
	get_full_path(IMAGE_PATH, filename, path);
	ret = init_effect(path, right_window_num);
	if (ret)
		return;
	show_image(effect_data.left, &effect_data,\
		effect_data.startx, effect_data.starty);
}

void handle_key_down(void)
{
	
}

void handle_key_up(void)
{
}

//example 9.6
static void morph_fill_background(void)
{
	int w, h;
	w = effect_data.image_width;
	h = effect_data.image_height;

	morph_label hole;
	hole.k0x = 0;
	hole.k0y = 0;
	//square area
	hole.sx = 0;
	hole.sy = 0;
	hole.ex = w;
	hole.ey = h;
	morph_label_background(effect_data.ori_data, effect_data.chg_data, &hole, gmorph_label_ary);

	//show changed
	int i, size;
	size = w * h;
	for(i = 0; i < size; i++) {
		if(gmorph_label_ary[i] == MORPH_START_LABEL) {
			//background
			effect_data.chg_data[i] = 0;
		} else if(effect_data.chg_data[i] == 0) {
			//internal black
			effect_data.chg_data[i] = 255;
		} else {
			//foreground
			effect_data.chg_data[i] = 255;
		}
	}
}

static void morph_fill_one_hole(void);

static void morph_fill_hole(void)
{
	int w, h;
	w = effect_data.image_width;
	h = effect_data.image_height;

	if(effect_data.order == 0) {
		hint_right = "fill one hole using 9.5.2, space key continue";
		effect_data.order++;
		gstate = STATE_WAIT_NEXT;
		morph_fill_one_hole();
		draw_right_data(effect_data.chg_data, 1);
	} else if(effect_data.order == 1) {
		clear_fb_right(&effect_data, RAYWHITE);
		hint_right = "fill all holes using 9.5.2, space key continue";
		effect_data.order++;

		morph_fill_background();
		draw_right_data(effect_data.chg_data, 1);
	} else if(effect_data.order == 2){
		effect_data.order++;
		clear_fb_right(&effect_data, RAYWHITE);
		hint_right = "find all connected components using 9.5.3";
		gstate = STATE_FINISH;

		morph_label_foreground(effect_data.ori_data, gmorph_label_ary, w, h, gmorph_label);
		draw_right_image(effect_data.ori, 1);
	}
}

//measured from original image
//one of point of hole(588, 176)
//center of hole: (582, 178), r = 18
static int g_hole_centerx = 0;
static int g_hole_cetnery = 0;

static int g_hole_recx = 582;
static int g_hole_recy = 178;
static int g_hole_r = 18;

static void morph_fill_one_hole(void)
{
	g_hole_centerx = 588;
	g_hole_cetnery = 176;

	int w, h, temp;
	w = effect_data.image_width;
	h = effect_data.image_height;

	morph_label hole;
	hole.k0x = g_hole_centerx - effect_data.rx;
	hole.k0y = h - (g_hole_cetnery - effect_data.starty);
	//square area
	hole.sx = g_hole_recx - effect_data.rx - g_hole_r;
	hole.sy = h - (g_hole_recy - effect_data.starty - g_hole_r);
	hole.ex = hole.sx + 2 * g_hole_r;
	hole.ey = hole.sy - 2 * g_hole_r;
	if(hole.sy > hole.ey) {
		//swap it
		temp = hole.sy;
		hole.sy = hole.ey;
		hole.ey = temp;
	}

	morph_hole_filling_four(effect_data.ori_data, effect_data.chg_data, &hole);
}

void handle_key_space(void)
{
	if(gstate != STATE_WAIT_NEXT)
		return;

	switch(effect_data.cmd) {
		case 5:
			morph_fill_hole();
			break;
		default:
			break;
	}
}

static void draw_erosion_image(FIBITMAP* ori, int dim, int pos)
{
	FIBITMAP* scaled;

	erosion_image(ori, effect_data.chg, dim);
	scaled = FreeImage_Rescale(effect_data.chg, effect_data.rw, effect_data.rh,
		FILTER_BILINEAR);

	draw_right_image(scaled, pos);
	close_img(scaled);
}

//example 9.4
static void morph_image_filter(void)
{
	FIBITMAP* temp = copy_img(effect_data.ori);

	//temp = A - B
	erosion_image_common(effect_data.ori, temp, SE_TYPE_SQUARE, 3);
	draw_right_image(temp, 1);

	//chg = temp + B
	dilation_image_common(temp, effect_data.chg, SE_TYPE_SQUARE, 3);
	draw_right_image(effect_data.chg, 2);

	//temp = chg + B
	dilation_image_common(effect_data.chg, temp, SE_TYPE_SQUARE, 3);
	draw_right_image(temp, 3);

	//chg = temp - B
	erosion_image_common(temp, effect_data.chg, SE_TYPE_SQUARE, 3);;
	draw_right_image(effect_data.chg, 4);

	close_img(temp);
}

//sort by connected components position: from left to right
static int  morph_compare_label(const void* a, const void* b)
{
	morph_label* ma = (morph_label*)a;
	morph_label* mb = (morph_label*)b;
	return (ma->sx - mb->sx);
}

//example 9.7
static void morph_extra_con_comp(void)
{
	//figure 1, threshold = 204, convert to binary image
	image_binary(effect_data.ori, effect_data.chg, 204);
	show_scaled(effect_data.chg, 1);

	//figure 2, erosion binary image with 5*5
	int w, h;
	w = effect_data.image_width;
	h = effect_data.image_height;
	init_image_datab(effect_data.chg, effect_data.ori_data);
	erosion_mem(effect_data.ori_data, effect_data.chg_data, w, h, 5);
	show_scaled_data(effect_data.chg_data, 2, effect_data.rw, effect_data.rh);

	//figure 3, extraction of connected components
	int cons = morph_label_foreground(effect_data.chg_data, gmorph_label_ary,
		w, h, gmorph_label); 
	qsort(gmorph_label, cons, sizeof(morph_label), morph_compare_label);
}

//Opening by reconstruction
static void morph_open_recon_show(void)
{
	int w, h;
	w = effect_data.image_width;
	h = effect_data.image_height;
	int dim = 51;

	//open op
	morph_open(effect_data.ori_data, effect_data.chg_data, SE_TYPE_VER, dim);
	show_scaled_datas(effect_data.chg_data, 2);

	//erode by 1*51
	erosion_ver(effect_data.ori_data, effect_data.chg_data, dim);
	show_scaled_datas(effect_data.chg_data, 1);

	/*
	* original image as mask(G)
	* eroded image as marker(F)
	* reconstruction SE = 3 * 3
	 * */
	morph_recon_dilation(effect_data.chg_data, effect_data.ori_data, gresult);
	show_scaled_datas(gresult, 3);
}

void special_effect(int cmd)
{
	//repeate press same key
	if (effect_data.cmd == cmd)
		return;

	if(cmd <= MAX_CMD_NUM) {
		effect_data.cmd = cmd;
		hint_right = (char*)command_str_en[cmd - 1];
		SetWindowTitle(command_str_cn[cmd - 1]);
		printf("%s\n", command_str_cn[cmd - 1]);
	}

	int w, h, size;
	switch (cmd) {
	case 1:
		switch_next_image("Fig0905(a)(wirebond-mask).tif", 4);
		draw_right_image(effect_data.right, 1);
		draw_erosion_image(effect_data.ori, 11, 2);
		draw_erosion_image(effect_data.ori, 15, 3);
		draw_erosion_image(effect_data.ori, 45, 4);
		break;
	case 2:
		switch_next_image("Fig0907(a)(text_gaps_1_and_2_pixels).tif", 1);
		w = effect_data.image_width;
		h = effect_data.image_height;
		dilation_fourcon(effect_data.ori_data, effect_data.chg_data);
		draw_right_data(effect_data.chg_data, 1);
		break;
	case 3:
		switch_next_image("Fig0911(a)(noisy_fingerprint).tif", 4);
		morph_image_filter();
		break;
	case 4:
		switch_next_image("Fig0914(a)(licoln from penny).tif", 1);
		morph_boundary_extraction(effect_data.ori, effect_data.chg,
			SE_TYPE_EIGHT_CON, 3);
		draw_right_image(effect_data.chg, 1);
		break;
	case 5:
		switch_next_image("Fig0916(a)(region-filling-reflections).tif", 1);
		morph_fill_hole();
		break;
	case 6:
		switch_next_image("Fig0918(a)(Chickenfilet with bones).tif", 4);
		morph_extra_con_comp();
		break;
	case 7:
		switch_next_image("Fig0929(a)(text_image).tif", 4);
		morph_open_recon_show();
		break;
	case 8:
		switch_next_image("Fig0929(a)(text_image).tif", 1);
		//switch_next_image("Fig0916(a)(region-filling-reflections).tif", 1);
		w = effect_data.image_width;
		h = effect_data.image_height;
		size = w * h;
		morph_get_hole_marker(effect_data.ori_data, effect_data.chg_data);
		complement_mem(effect_data.ori_data, size);

		morph_recon_dilation(effect_data.chg_data, effect_data.ori_data, gresult);
		complement_mem(gresult, size);
		show_scaled_datas(gresult, 1);
		break;
	case 9:
		switch_next_image("Fig0929(a)(text_image).tif", 1);
		w = effect_data.image_width;
		h = effect_data.image_height;
		size = w * h;
		morph_get_clear_marker(effect_data.ori_data, effect_data.chg_data);
		morph_recon_dilation(effect_data.chg_data, effect_data.ori_data, gresult);

		mem_add(effect_data.ori_data, gresult, gresult, -1, w, h);
		show_scaled_datas(gresult, 1);
		break;
	case KEY_DOWN:
		handle_key_down();
		break;
	case KEY_UP:
		handle_key_up();
		break;
	case KEY_SPACE:
		handle_key_space();
		break;
	default:
		hint_right = "not support command";
		break;
	}//end switch
}

static void draw_select_rectangle(void)
{
	if(g_hole_centerx && effect_data.order == 1) {
		DrawCircleLines(g_hole_centerx, g_hole_cetnery, 5, RED);
		DrawRectangleLines(g_hole_recx - g_hole_r, g_hole_recy - g_hole_r,
			g_hole_r * 2, g_hole_r * 2, BLUE);
	}

	if(effect_data.order == 3) {
		morph_label *label = gmorph_label;
		while(label->label != MORPH_END_LABEL) {
			DrawRectangleLines(label->sx + effect_data.rx,
				effect_data.image_height - (label->ey - effect_data.starty),
				label->ex - label->sx + 1,
				label->ey - label->sy + 1, BLUE);
			label++;
		}
	}
}

static void morph_draw_stats(void)
{
	morph_label* label = gmorph_label;
	int x = effect_data.rx;
	int sy = 220;
	int height = 22;
	int ly = sy;
	int lx = 600;
	DrawText("Connected", x, ly, 20, BROWN);
	DrawText("No. of pixels in", lx, ly, 20, BROWN);
	ly += height;
	DrawText("component", x, ly, 20, BROWN);
	DrawText("connected comp", lx, ly, 20, BROWN);
	ly += height;

	char label_str[4];
	char value[8];
	int i = 1;
	while (label->label != MORPH_END_LABEL) {
		sprintf(label_str, "%.2d", i);
		sprintf(value, "%d", label->pixels);
		DrawText(label_str, x + 50, ly, 20, BLACK);
		DrawText(value, lx + 50, ly, 20, BLACK);
		ly += height;
		label++;
		i++;
	}
}

static void draw_titles(void)
{
	switch(effect_data.cmd) {
		case 1:
			draw_title("ori", 1);
			draw_title("11*11 erosion", 2);
			draw_title("15*15 erosion", 3);
			draw_title("45*45 erosion", 4);
			break;
		case 3: //example 9.4
			draw_title("Eroded image", 1);
			draw_title("Opening of A", 2);
			draw_title("Dilation of the Opening", 3);
			draw_title("Closing of the Opening", 4);
			break;
		case 5: //example 9.6
			draw_select_rectangle();
			break;
		case 6: //example 9.7
			draw_title("Threshold value = 204, 9.18b", 1);
			draw_title("eroded with 5*5, 9.18c", 2);
			morph_draw_stats();
			break;
		case 7:
			draw_title("Erosion with SE 51 * 1", 1);
			draw_title("Opening with SE 51 * 1", 2);
			draw_title("Opening by reconstruction", 3);
			break;
		default:
			break;
	}
}

int main(int argc, char** argv)
{
	// Initialization
	//--------------------------------------------------------------------------------------
	FreeImage_Initialise(TRUE);
	init_effect_data();
	init_fb();

	InitWindow(screenWidth, screenHeight, "demo");
	// NOTE: Textures MUST be loaded after Window initialization (OpenGL context is required)
	SetTargetFPS(60);               // Set our game to run at 60 frames-per-second
	//--------------------------------------------------------------------------------------

	int key = 0;
	int change = 0;
	Vector2 mouse = GetMousePosition();
	hint_right = "press number key";
	print_info_cn(command_str_cn, MAX_CMD_NUM);

	// Main game loop
	while (!WindowShouldClose())    // Detect window close button or ESC key
	{
		// Update
		key = GetKeyPressed();
		if (key >= '1' && key <= '9') {
			change = 1;
			key = key - '0';
		} else if(key == KEY_DOWN || key == KEY_UP){
			change = 1;
		}
		else if(key == KEY_SPACE) {
			change = 1;
		}
		else if (key == ESC_KEY) {
			break;
		}

		if (change) {
			change = 0;
			special_effect(key);
		}
		//----------------------------------------------------------------------------------
		//----------------------------------------------------------------------------------

		// Draw
		//----------------------------------------------------------------------------------
		BeginDrawing();
		ClearBackground(RAYWHITE);

		DrawText(hint_left, effect_data.startx, 5, 20, BLACK);
		if (hint_right) {
			DrawText(hint_right, effect_data.rx, 0, 20, BLACK);
		}
		show_info_en(command_str_en, MAX_CMD_NUM, effect_data.startx,\
			effect_data.left_max + effect_data.starty + 20);
		draw_image(effect_data.frame_buf, effect_data.win_width, effect_data.win_height);
		DrawLine(effect_data.split_line, effect_data.starty,\
			effect_data.split_line, GetScreenHeight(), GRAY);
		draw_titles();

		mouse = GetMousePosition();
		DrawText(TextFormat("mouse(%d, %d)", (int)mouse.x, (int)mouse.y), \
			GetScreenWidth() - 200, 10, 20, BLACK);

		EndDrawing();
		//----------------------------------------------------------------------------------
	}

	// De-Initialization
	destroy_effect();
	FreeImage_DeInitialise();

	//--------------------------------------------------------------------------------------
	CloseWindow();        // Close window and OpenGL context
	//--------------------------------------------------------------------------------------

	return 0;
}


